/**
 * 
 */
package org.swing.utility.net.packet;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;

import org.swing.utility.net.bundle.OSCBundle;
import org.swing.utility.net.message.OSCMessage;

/**
 * @author lqnhu
 *
 */
public abstract class OSCPacket {
	// used for hexdump
	private static final byte[] hex = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
			0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 };

	/**
	 * Constructs a new <code>OSCPacket</code>. Refer to the subclasses for the
	 * particular way they instantiate the class
	 */
	protected OSCPacket() { /* empty */
	}

	/**
	 * Calculates and returns the packet's size in bytes, using the default
	 * codec.
	 *
	 * @return the size of the packet in bytes, including the initial osc
	 *         command and aligned to 4-byte boundary. this is the amount of
	 *         bytes written by the <code>encode</code> method.
	 *
	 * @throws IOException
	 *             if an error occurs during the calculation
	 * @see #getSize(OSCPacketCodec )
	 */
	public final int getSize() throws IOException {
		return getSize(OSCPacketCodec.getDefaultCodec());
	}

	/**
	 * Calculates and returns the packet's size in bytes, using a given codec.
	 *
	 * @param c
	 *            the codec that is shall used for encoding the packet
	 * @return the size of the packet in bytes, including the initial osc
	 *         command and aligned to 4-byte boundary. this is the amount of
	 *         bytes written by the <code>encode</code> method.
	 *
	 * @throws IOException
	 *             if an error occurs during the calculation
	 * @see OSCPacketCodec#getSize(OSCPacket )
	 */
	public final int getSize(OSCPacketCodec c) throws IOException {
		return c.getSize(this);
	}

	/**
	 * Encodes the contents of this packet into the provided
	 * <code>ByteBuffer</code>, beginning at the buffer's current position,
	 * using the default codec. To write the encoded packet, you will typically
	 * call <code>flip()</code> on the buffer, then <code>write()</code> on the
	 * channel.
	 *
	 * @param b
	 *            <code>ByteBuffer</code> pointing right at the beginning of the
	 *            osc packet. buffer position will be right after the end of the
	 *            packet when the method returns.
	 *
	 * @throws IOException
	 *             in case some of the writing procedures failed.
	 * @see #encode(OSCPacketCodec, ByteBuffer )
	 */
	public final void encode(ByteBuffer b) throws IOException {
		encode(OSCPacketCodec.getDefaultCodec(), b);
	}

	/**
	 * Encodes the contents of this packet into the provided
	 * <code>ByteBuffer</code>, beginning at the buffer's current position,
	 * using a given codec. To write the encoded packet, you will typically call
	 * <code>flip()</code> on the buffer, then <code>write()</code> on the
	 * channel.
	 *
	 * @param b
	 *            <code>ByteBuffer</code> pointing right at the beginning of the
	 *            osc packet. buffer position will be right after the end of the
	 *            packet when the method returns.
	 * @param c
	 *            the codec that is shall used for encoding the packet
	 *
	 * @throws IOException
	 *             in case some of the writing procedures failed.
	 * @see OSCPacketCodec#encode(OSCPacket, ByteBuffer )
	 */
	public final void encode(OSCPacketCodec c, ByteBuffer b) throws IOException {
		c.encode(this, b);
	}

	/**
	 * Creates a new packet decoded from the ByteBuffer, using the default
	 * codec. This method simply calls the equivalent method in
	 * <code>OSCPacketCodec</code>.
	 *
	 * @param b
	 *            <code>ByteBuffer</code> pointing right at the beginning of the
	 *            packet. the buffer's limited should be set appropriately to
	 *            allow the complete packet to be read. when the method returns,
	 *            the buffer's position is right after the end of the packet.
	 *
	 * @return new decoded OSC packet
	 *
	 * @throws IOException
	 *             in case some of the reading or decoding p rocedures failed.
	 * @throws IllegalArgumentException
	 *             occurs in some cases of buffer underflow
	 * @see OSCPacketCodec#decode(ByteBuffer )
	 */
	// public static OSCPacket decode( ByteBuffer b, Map m )
	public static OSCPacket decode(ByteBuffer b) throws IOException {
		return OSCPacketCodec.getDefaultCodec().decode(b);
	}

	/**
	 * Prints a text version of a packet to a given stream. The format is
	 * similar to scsynth using dump mode 1. Bundles will be printed with each
	 * message on a separate line and increasing indent.
	 *
	 * @param stream
	 *            the print stream to use, for example <code>System.out</code>
	 * @param p
	 *            the packet to print (either a message or bundle)
	 */
	public static void printTextOn(PrintStream stream, OSCPacket p) {
		OSCPacket.printTextOn(stream, p, 0);
	}

	/**
	 * Prints a hexdump version of a packet to a given stream. The format is
	 * similar to scsynth using dump mode 2. Unlike <code>printTextOn</code>
	 * this takes a raw received or encoded byte buffer and not a decoded
	 * instance of <code>OSCPacket</code>.
	 *
	 * @param stream
	 *            the print stream to use, for example <code>System.out</code>
	 * @param b
	 *            the byte buffer containing the packet. read position should be
	 *            at the very beginning of the packet, limit should be at the
	 *            end of the packet. this method alters the buffer position in a
	 *            manner that a successive <code>flip()</code> will restore the
	 *            original position and limit.
	 *
	 * @see java.nio.Buffer#limit()
	 * @see java.nio.Buffer#position()
	 */
	public static void printHexOn(PrintStream stream, ByteBuffer b) {
		final int lim = b.limit(); // len = b.limit() - off;
		final byte[] txt = new byte[74];
		int i, j, k, n, m;
		for (i = 4; i < 56; i++) {
			txt[i] = (byte) 0x20;
		}
		txt[56] = (byte) 0x7C;
		stream.println();
		for (i = b.position(); i < lim;) {
			j = 0;
			txt[j++] = hex[(i >> 12) & 0xF];
			txt[j++] = hex[(i >> 8) & 0xF];
			txt[j++] = hex[(i >> 4) & 0xF];
			txt[j++] = hex[i & 0xF];
			m = 57;
			for (k = 0; k < 16 && i < lim; k++, i++) {
				if ((k & 7) == 0)
					j += 2;
				else
					j++;
				n = b.get();
				txt[j++] = hex[(n >> 4) & 0xF];
				txt[j++] = hex[n & 0xF];
				txt[m++] = (n > 0x1F) && (n < 0x7F) ? (byte) n : (byte) 0x2E;
			}
			txt[m++] = (byte) 0x7C;
			while (j < 54) {
				txt[j++] = (byte) 0x20;
			}
			while (m < 74) {
				txt[m++] = (byte) 0x20;
			}
			stream.write(txt, 0, 74);
			stream.println();
		}
		stream.println();
	}

	private static void printTextOn(PrintStream stream, OSCPacket p,
			int nestCount) {
		OSCMessage msg;
		OSCBundle bndl;
		Object o;
		if (p instanceof OSCMessage) {
			msg = (OSCMessage) p;
			for (int i = 0; i < nestCount; i++)
				stream.print(" ");
			stream.print("[ \"" + msg.getName() + "\"");
			for (int i = 0; i < msg.getArgCount(); i++) {
				o = msg.getArg(i);
				if (o instanceof Number) {
					if (o instanceof Float || o instanceof Double) {
						stream.print(", " + ((Number) o).floatValue());
					} else {
						stream.print(", " + ((Number) o).longValue());
					}
				} else if (o instanceof OSCPacket) {
					stream.println(",");
					printTextOn(stream, (OSCPacket) o, nestCount + 1);
				} else if (o instanceof byte[]) {
					stream.print(", DATA[" + ((byte[]) o).length + "]");
				} else {
					stream.print(", \"" + o.toString() + "\"");
				}
			}
			stream.print(" ]");
		} else {
			bndl = (OSCBundle) p;
			for (int i = 0; i < nestCount; i++)
				stream.print(" ");
			stream.print("[ \"#bundle\"");
			for (int i = 0; i < bndl.getPacketCount(); i++) {
				stream.println(",");
				OSCPacket.printTextOn(stream, bndl.getPacket(i), nestCount + 1);
			}
			for (int i = 0; i < nestCount; i++)
				stream.print(" ");
			stream.print("]");
		}
		if (nestCount == 0)
			stream.println();
	}
}
